/**

    @file ESP8266HTTPUpdate.h
    @date 21.06.2015
    @author Markus Sattler

    Copyright (c) 2015 Markus Sattler. All rights reserved.
    This file is part of the ESP8266 Http Updater.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

#pragma once

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#include <HTTPClient.h>

#ifdef DEBUG_RP2040_CORE
#ifdef DEBUG_RP2040_PORT
#define DEBUG_HTTP_UPDATE(fmt, ...) DEBUG_RP2040_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
#endif
#endif

#ifndef DEBUG_HTTP_UPDATE
#define DEBUG_HTTP_UPDATE(...) do { (void)0; } while(0)
#endif

/// note we use HTTP client errors too so we start at 100
//TODO - in v3.0.0 make this an enum
constexpr int HTTP_UE_TOO_LESS_SPACE            = (-100);
constexpr int HTTP_UE_SERVER_NOT_REPORT_SIZE    = (-101);
constexpr int HTTP_UE_SERVER_FILE_NOT_FOUND     = (-102);
constexpr int HTTP_UE_SERVER_FORBIDDEN          = (-103);
constexpr int HTTP_UE_SERVER_WRONG_HTTP_CODE    = (-104);
constexpr int HTTP_UE_SERVER_FAULTY_MD5         = (-105);
constexpr int HTTP_UE_BIN_VERIFY_HEADER_FAILED  = (-106);
constexpr int HTTP_UE_BIN_FOR_WRONG_FLASH       = (-107);
constexpr int HTTP_UE_SERVER_UNAUTHORIZED       = (-108);

enum HTTPUpdateResult {
    HTTP_UPDATE_FAILED,
    HTTP_UPDATE_NO_UPDATES,
    HTTP_UPDATE_OK
};

typedef HTTPUpdateResult t_httpUpdate_return; // backward compatibility

using HTTPUpdateStartCB = std::function<void()>;
using HTTPUpdateEndCB = std::function<void()>;
using HTTPUpdateErrorCB = std::function<void(int)>;
using HTTPUpdateProgressCB = std::function<void(int, int)>;

class HTTPUpdate {
public:
    HTTPUpdate(void);
    HTTPUpdate(int httpClientTimeout);
    ~HTTPUpdate(void);

    void rebootOnUpdate(bool reboot) {
        _rebootOnUpdate = reboot;
    }

    /**
        set true to follow redirects.
        @param follow
        @deprecated Please use `setFollowRedirects(followRedirects_t follow)`
    */
    void followRedirects(bool follow) __attribute__((deprecated)) {
        _followRedirects = follow ? HTTPC_STRICT_FOLLOW_REDIRECTS : HTTPC_DISABLE_FOLLOW_REDIRECTS;
    }
    /**
        set redirect follow mode. See `followRedirects_t` enum for available modes.
        @param follow
    */
    void setFollowRedirects(followRedirects_t follow) {
        _followRedirects = follow;
    }

    void closeConnectionsOnUpdate(bool sever) {
        _closeConnectionsOnUpdate = sever;
    }

    void setMD5sum(const String &md5Sum) {
        _md5Sum = md5Sum;
    }

    void setAuthorization(const String& user, const String& password);
    void setAuthorization(const String& auth);

    t_httpUpdate_return update(WiFiClient& client, const String& url, const String& currentVersion = "");
    t_httpUpdate_return update(WiFiClient& client, const String& host, uint16_t port, const String& uri = "/",
                               const String& currentVersion = "");
    t_httpUpdate_return updateFS(WiFiClient& client, const String& url, const String& currentVersion = "");

    t_httpUpdate_return update(const String& url, const String& currentVersion = "");
    t_httpUpdate_return update(const String& host, uint16_t port, const String& uri = "/", const String& currentVersion = "");
    t_httpUpdate_return updateFS(const String& url, const String& currentVersion = "");

    // Notification callbacks
    void onStart(HTTPUpdateStartCB cbOnStart)          {
        _cbStart = cbOnStart;
    }
    void onEnd(HTTPUpdateEndCB cbOnEnd)                {
        _cbEnd = cbOnEnd;
    }
    void onError(HTTPUpdateErrorCB cbOnError)          {
        _cbError = cbOnError;
    }
    void onProgress(HTTPUpdateProgressCB cbOnProgress) {
        _cbProgress = cbOnProgress;
    }

    int getLastError(void);
    String getLastErrorString(void);

protected:
    t_httpUpdate_return handleUpdate(HTTPClient& http, const String& currentVersion, bool spiffs = false);
    bool runUpdate(Stream& in, uint32_t size, const String& md5, int command = U_FLASH);

    // Set the error and potentially use a CB to notify the application
    void _setLastError(int err) {
        _lastError = err;
        if (_cbError) {
            _cbError(err);
        }
    }
    int _lastError;
    bool _rebootOnUpdate = true;
    bool _closeConnectionsOnUpdate = true;
    String _user;
    String _password;
    String _auth;
    String _md5Sum;
private:
    int _httpClientTimeout;
    followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;

    // Callbacks
    HTTPUpdateStartCB    _cbStart;
    HTTPUpdateEndCB      _cbEnd;
    HTTPUpdateErrorCB    _cbError;
    HTTPUpdateProgressCB _cbProgress;
};

#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE)
extern HTTPUpdate httpUpdate;
#endif
